﻿using System;
using System.Collections.Generic;
using DepIC.Entities;
using DepIC.Helpers;

namespace DepIC
{
    internal class Container : IContainer
    {
        #region Fields

        private readonly object _syncRoot = new object();

        private IDictionary<Type, Implementation> _implementations;
        private IDictionary<Type, Instance> _instances;

        #endregion


        #region Constructors
        
        public Container()
        {
            _implementations = new Dictionary<Type, Implementation>();

            _instances =
                new Dictionary<Type, Instance>
                    {
                        {typeof (IReadOnlyContainer), new Instance(this)},
                        {typeof (IContainer), new Instance(this)}
                    };
        }

        #endregion


        #region Public Methods

        public void SetImplementation<TKey, TImplementation>(Lifetime lifetime = Lifetime.PerCall) 
            where TImplementation : class, TKey
        {
            SetImplementation(typeof (TKey), typeof (TImplementation), lifetime);
        }

        public void SetImplementation(Type keyType, Type implementationType, Lifetime lifetime = Lifetime.PerCall)
        {
            Checker.ArgumentIsNull(keyType, "keyType");
            Checker.ArgumentIsNull(implementationType, "implementationType");

            if (!keyType.IsAssignableFrom(implementationType))
                throw new ContainerException("Type '{0}' must be derived from type '{1}'.", implementationType, keyType);

            lock (_syncRoot)
            {
                if (Contains(keyType))
                    throw new ContainerException("Container already contains '{0}' type as a key.", keyType);

                var implementation = new Implementation(implementationType, lifetime);

                _implementations.Add(keyType, implementation);
            }
        }

        public void SetConstant<TKey, TConstant>(TConstant constant) 
            where TConstant : class, TKey
        {
            SetConstant(typeof(TKey), constant);
        }

        public void SetConstant(Type keyType, object constant)
        {
            Checker.ArgumentIsNull(keyType, "keyType");
            Checker.ArgumentIsNull(constant, "constant");

            if (!keyType.IsInstanceOfType(constant))
                throw new ContainerException("Type '{0}' must be derived from type '{1}'.", constant.GetType(), keyType);

            lock (_syncRoot)
            {
                if (Contains(keyType))
                    throw new ContainerException("Container already contains '{0}' type as a key.", keyType);

                var instance = new Instance(constant);

                _instances.Add(keyType, instance);
            }
        }

        public void Remove<TKey>()
        {
            Remove(typeof(TKey));
        }

        public void Remove(Type keyType)
        {
            Checker.ArgumentIsNull(keyType, "keyType");

            if (keyType == typeof(IReadOnlyContainer) || keyType == typeof(IContainer))
                throw new ContainerException("The key type '{0}' cannot be removed from container.", keyType);

            lock (_syncRoot)
            {
                if (!Contains(keyType))
                    throw new ContainerException("Container does not contain '{0}' type as a key.", keyType);

                if (_instances.ContainsKey(keyType))
                {
                    _instances.Remove(keyType);
                    return;
                }

                _implementations.Remove(keyType);
            }
        }

        public bool Contains<TKey>()
        {
            return Contains(typeof (TKey));
        }

        public bool Contains(Type keyType)
        {
            Checker.ArgumentIsNull(keyType, "keyType");

            return _instances.ContainsKey(keyType) || _implementations.ContainsKey(keyType);
        }

        public TKey Get<TKey>()
        {
            return (TKey) Get(typeof (TKey));
        }

        public object Get(Type keyType)
        {
            Checker.ArgumentIsNull(keyType, "keyType");

            lock (_syncRoot)
            {
                if (!Contains(keyType))
                    throw new ContainerException("Container does not contain '{0}' type as a key.", keyType);

                {
                    Instance instance;
                    if (_instances.TryGetValue(keyType, out instance))
                        return instance.Object;
                }

                {
                    var implementation = _implementations[keyType];
                    var instance = InstanceCreator.Create(this, implementation.Type);
                    PropertyInjector.Inject(this, instance);

                    if (implementation.Lifetime == Lifetime.PerContainer)
                    {
                        _instances.Add(keyType, instance);
                        _implementations.Remove(keyType);
                    }

                    return instance.Object;
                }
            }
        }

        public T CreateInstance<T>()
        {
            return (T) CreateInstance(typeof (T));
        }

        public object CreateInstance(Type type)
        {
            Checker.ArgumentIsNull(type, "type");

            var instance = InstanceCreator.Create(this, type);
            PropertyInjector.Inject(this, instance);

            return instance.Object;
        }

        public object SyncRoot
        {
            get { return _syncRoot; }
        }

        public void Dispose()
        {
            _implementations = null;
            _instances = null;
        }

        #endregion
    }
}
